// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/binance_futures/api_order/native_private_client.h"

#include <memory>
#include <string>

#include <rapidjson/writer.h>

#include "coin2/exchange/base/api_util/url_params_builder.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/binance_base/api_util/rate_tracker.h"
#include "coin2/exchange/binance_futures/symbology/product.h"

#define CHECK_API_LIMIT(action_type)                                                      \
  if (!HasEnoughRateLimit(ts, action_type)) {                                             \
    LOG_EVERY_N(WARNING, 50) << "Hit rate limit or api temporary disabled, " << __func__; \
    api_block_info_[__func__]++;                                                          \
    return {};                                                                            \
  }

namespace {
using ActionType = binance::api_util::BinanceRateTracker::ActionType;
using coin2::exchange::binance::api_util::BinanceRateTracker;
}  // namespace

namespace coin2::exchange::binance_futures::api_order {

auto BinanceFuturesNativePrivateClient::GetRestOrderParams(
    const OrderSpec& order_spec,
    int64_t proc_order_id) -> RestParams {
  auto params = RestParams();

  using coin2::exchange::binance_futures::symbology::BinanceFuturesProduct;
  auto product = BinanceFuturesProduct::FromStr(order_spec.product, order_spec.order_created_time);
  auto product_holder = GetProductHolder(product, order_spec.order_created_time);
  auto formatter = product_holder->product_info().order_formatter();
  params.set_price(formatter.FormatPrice(order_spec.price));
  params.set_quantity(formatter.FormatQty(order_spec.qty));
  params.set_symbol(order_spec.product);
  params.set_new_client_order_id(std::to_string(proc_order_id));

  if (order_spec.order_type == OrderType::MARKET_ORDER) {
    params.set_type("MARKET");
  } else {
    params.set_type("LIMIT");
  }

  if (order_spec.order_side == OrderSide::BUY_ORDER) {
    params.set_side("BUY");
  } else if (order_spec.order_side == OrderSide::SELL_ORDER) {
    params.set_side("SELL");
  } else {
    NOTREACHED() << "Unknown order side: " << static_cast<int>(order_spec.order_side);
  }

  if (order_spec.duration == coin::proto::OrderDuration::FOK_ORDER) {
    params.set_time_in_force("FOK");
  } else if (order_spec.duration == coin::proto::OrderDuration::IOC_ORDER) {
    params.set_time_in_force("IOC");
  } else {
    if (order_spec.post_only) {
      params.set_time_in_force("GTX");
    } else {
      params.set_time_in_force("GTC");
    }
  }
  if (order_spec.reduce_only) {
    params.set_reduce_only("true");
  } else {
    params.set_reduce_only("false");
  }
  params.set_recv_window(5000);

  return params;
}

auto BinanceFuturesNativePrivateClient::GetRestCancelParams(const IOrderContext& order_context)
    -> RestParams {
  auto params = RestParams();
  const std::string& product_str = order_context.order_spec().product;
  params.set_symbol(product_str);
  if (order_context.has_exchange_order_id()) {
    params.set_order_id(order_context.exchange_order_id().substr(product_str.length()));
  } else {
    params.set_orig_client_order_id(std::to_string(order_context.proc_order_id()));
  }

  params.set_recv_window(5000);
  return params;
}

BinanceFuturesNativePrivateClient::BinanceFuturesNativePrivateClient(
    const MarketExchangeApi& mea,
    const OrderConnectionConfig& config_override,
    bool is_delivery)
    : is_delivery_{is_delivery} {
  mea_ = mea;
  // default config
  order::OrderConnectionConfig config;
  if (is_delivery_) {
    config.set_rest_host("dapi.binance.com");
  } else {
    config.set_rest_host("fapi.binance.com");
  }
  config.set_rest_host_port(443);
  // user supplied config override default config
  config.MergeFrom(config_override);
  LOG(INFO) << "[BinanceFuturesNativePrivateClient] config: " << config.DebugString();

  auths_.Construct<BinanceRestAuth, order::OrderConnectionConfig>(config);

  if (is_delivery_) {
    account_path_ = "/dapi/v1/account";
    open_orders_path_ = "/dapi/v1/openOrders";
    query_fill_path_ = "/dapi/v1/userTrades";
    history_orders_path_ = "/dapi/v1/allOrders";
    query_transfer_path_ = "/sapi/v1/futures/transfer";
    query_funding_fee_path_ = "/dapi/v1/income";
    place_single_order_path_ = "/dapi/v1/order";
    place_batch_orders_path_ = "/dapi/v1/batchOrders";
    listen_path_ = "/dapi/v1/listenKey";
  } else {
    account_path_ = "/fapi/v2/account";
    open_orders_path_ = "/fapi/v1/openOrders";
    query_fill_path_ = "/fapi/v1/userTrades";
    history_orders_path_ = "/fapi/v1/allOrders";
    query_transfer_path_ = "/sapi/v1/futures/transfer";
    query_funding_fee_path_ = "/fapi/v1/income";
    place_single_order_path_ = "/fapi/v1/order";
    place_batch_orders_path_ = "/fapi/v1/batchOrders";
    listen_path_ = "/fapi/v1/listenKey";
  }
  rate_tracker_ = std::make_unique<BinanceRateTracker>(mea_, config);
  start_time_ = GetCurrentTimestamp();

  // for query cross margin
  config.set_rest_host("api.binance.com");
  margin_auths_.Construct<BinanceRestAuth, order::OrderConnectionConfig>(config);
}

BaseRestAuth* BinanceFuturesNativePrivateClient::GetAuth() const {
  return auths_.GetRandomTryNotUseFirst();
}

void BinanceFuturesNativePrivateClient::SetBindIp(const std::string& ip) {
  static_cast<BinanceRateTracker*>(rate_tracker_.get())->SetBindIp(ip);
}

std::shared_ptr<AsioHttpContext>
BinanceFuturesNativePrivateClient::QueryAccountBalanceAndAccountPosition(const RestParams& params) {
  auto ts = GetCurrentTimestamp();

  UrlParamsBuilder builder;
  builder.PutGet("recvWindow", params.recv_window());
  builder.PutGet("timestamp", GetCurrMilliseconds());
  auto context = std::shared_ptr<AsioHttpContext>(
      GetAuth()->CreateRequestByGetWithSignature(account_path_, &builder));
  SetBindIp(context->bind_ip);
  CHECK_API_LIMIT(ActionType::QUERY);
  Push(5, ts, ActionType::QUERY);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::REST_ACCOUNT_BALANCE;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::QueryCrossMargin(
    const RestParams& params) {
  UrlParamsBuilder builder;
  builder.PutGet("recvWindow", params.recv_window());
  builder.PutGet("timestamp", GetCurrMilliseconds());
  auto context =
      std::shared_ptr<AsioHttpContext>(margin_auths_.GetRandom()->CreateRequestByGetWithSignature(
          "/sapi/v1/margin/account",
          &builder));
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::REST_ACCOUNT_CROSS_BALANCE;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::QueryOpenOrders(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  int weight = 40;  // by sub-account
  UrlParamsBuilder builder;
  if (params.has_symbol() && !params.symbol().empty()) {
    builder.PutGet("symbol", params.symbol());
    weight = 1;  // by symbol
  }
  builder.PutGet("recvWindow", params.recv_window());
  builder.PutGet("timestamp", GetCurrMilliseconds());
  auto context = std::shared_ptr<AsioHttpContext>(
      GetAuth()->CreateRequestByGetWithSignature(open_orders_path_, &builder));
  SetBindIp(context->bind_ip);
  CHECK_API_LIMIT(ActionType::QUERY);
  Push(weight, ts, ActionType::QUERY);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::REST_OPEN_ORDERS;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::QueryAllOrders(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();

  UrlParamsBuilder builder;
  builder.PutGet("symbol", params.symbol());
  builder.PutGet("recvWindow", params.recv_window());
  builder.PutGet("timestamp", GetCurrMilliseconds());
  builder.PutGet("limit", params.limit());
  auto context = std::shared_ptr<AsioHttpContext>(
      GetAuth()->CreateRequestByGetWithSignature(history_orders_path_, &builder));

  SetBindIp(context->bind_ip);
  CHECK_API_LIMIT(ActionType::QUERY);

  if (is_delivery_) {
    Push(20, ts, ActionType::QUERY);
  } else {
    Push(5, ts, ActionType::QUERY);
  }
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::REST_ALL_ORDERS;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::QueryTrades(
    const RestParams& params) {
  DLOG_EVERY_N(INFO, 50) << DebugString();
  auto ts = GetCurrentTimestamp();

  UrlParamsBuilder builder;
  builder.PutGet("symbol", params.symbol());
  builder.PutGet("recvWindow", params.recv_window());
  builder.PutGet("timestamp", GetCurrMilliseconds());
  builder.PutGet("limit", params.limit());
  auto context = std::shared_ptr<AsioHttpContext>(
      GetAuth()->CreateRequestByGetWithSignature(query_fill_path_, &builder));

  SetBindIp(context->bind_ip);
  CHECK_API_LIMIT(ActionType::QUERY);

  if (is_delivery_) {
    Push(20, ts, ActionType::QUERY);
  } else {
    Push(5, ts, ActionType::QUERY);
  }
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::REST_FILL_ORDERS;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::QueryTransfer(
    const std::string& asset) {
  auto ts = GetCurrentTimestamp();
  UrlParamsBuilder builder;
  builder.PutGet("asset", asset);
  int64_t cur_millisec = std::stoll(GetCurrMilliseconds());
  builder.PutGet("startTime", std::to_string(cur_millisec - 30LL * 86400 * 1000));
  builder.PutGet("size", 1);
  builder.PutGet("recvWindow", 5000);
  builder.PutGet("timestamp", GetCurrMilliseconds());
  BinanceRestAuth* binance_auth = dynamic_cast<BinanceRestAuth*>(GetAuth());
  std::string host = binance_auth->GetHost();
  binance_auth->SetHost("api.binance.com");
  auto context = std::shared_ptr<AsioHttpContext>(
      binance_auth->CreateRequestByGetWithSignature(query_transfer_path_, &builder));
  binance_auth->SetHost(host);
  Push(1, ts, ActionType::QUERY);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::TRANSFER;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::QueryFundingFee() {
  auto ts = GetCurrentTimestamp();

  UrlParamsBuilder builder;
  int64_t cur_millisec = std::stoll(GetCurrMilliseconds());
  builder.PutGet("endTime", cur_millisec);
  builder.PutGet("incomeType", "FUNDING_FEE");
  builder.PutGet("recvWindow", 5000);
  builder.PutGet("timestamp", cur_millisec);
  builder.PutGet("limit", 1000);

  auto context = std::shared_ptr<AsioHttpContext>(
      GetAuth()->CreateRequestByGetWithSignature(query_funding_fee_path_, &builder));

  SetBindIp(context->bind_ip);
  CHECK_API_LIMIT(ActionType::QUERY);

  if (is_delivery_) {
    Push(20, ts, ActionType::QUERY);
  } else {
    Push(30, ts, ActionType::QUERY);
  }
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::REST_FUNDING_FEE;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::PlaceSingleOrder(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();
  UrlParamsBuilder builder;
  builder.PutGet("symbol", params.symbol());
  builder.PutGet("side", params.side());
  builder.PutGet("type", params.type());
  builder.PutGet("timeInForce", params.time_in_force());
  builder.PutGet("price", params.price());
  builder.PutGet("quantity", params.quantity());
  builder.PutGet("quantityOrderQty", params.quantity_order_qty());
  builder.PutGet("newClientOrderId", params.new_client_order_id());
  builder.PutGet("stopPrice", params.stop_price());
  builder.PutGet("icebergQty", params.iceberg_qty());
  builder.PutGet("newOrderRespType", params.new_order_resp_type());
  builder.PutGet("recvWindow", params.recv_window());
  builder.PutGet("timestamp", GetCurrMilliseconds());
  builder.PutGet("reduceOnly", params.reduce_only());
  auto context = std::shared_ptr<AsioHttpContext>(
      GetFirstAuth()->CreateRequestByPostWithSignature(place_single_order_path_, &builder));
  SetBindIp(context->bind_ip);
  CHECK_API_LIMIT(ActionType::PLACE);
  Push(1, ts, ActionType::PLACE);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::SUBMIT_RESPONSE;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::PlaceBatchOrders(
    const std::vector<RestParams>& params) {
  auto ts = GetCurrentTimestamp();

  int recv_window = 5000;
  rapidjson::StringBuffer str_buf;
  rapidjson::Writer<rapidjson::StringBuffer> writer(str_buf);
  writer.StartArray();

  for (const auto& param : params) {
    writer.StartObject();

    writer.Key("symbol");
    writer.String(param.symbol().c_str());
    writer.Key("side");
    writer.String(param.side().c_str());
    writer.Key("type");
    writer.String(param.type().c_str());
    writer.Key("timeInForce");
    writer.String(param.time_in_force().c_str());
    writer.Key("price");
    writer.String(param.price().c_str());
    writer.Key("quantity");
    writer.String(param.quantity().c_str());
    writer.Key("quantityOrderQty");
    writer.String(param.quantity_order_qty().c_str());
    writer.Key("newClientOrderId");
    writer.String(param.new_client_order_id().c_str());
    writer.Key("icebergQty");
    writer.String(param.iceberg_qty().c_str());
    writer.Key("newOrderRespType");
    writer.String(param.new_order_resp_type().c_str());
    writer.Key("reduceOnly");
    writer.String(param.reduce_only().c_str());

    writer.EndObject();

    recv_window = std::min(recv_window, param.recv_window());
  }

  writer.EndArray();

  UrlParamsBuilder builder;
  builder.PutGet("recvWindow", recv_window);
  builder.PutGet("timestamp", GetCurrMilliseconds());
  builder.PutGet("batchOrders", ApiSignatureUtil::EscapeURL(str_buf.GetString()));

  auto context = std::shared_ptr<AsioHttpContext>(
      GetFirstAuth()->CreateRequestByPostWithSignature(place_batch_orders_path_, &builder));
  SetBindIp(context->bind_ip);
  CHECK_API_LIMIT(ActionType::PLACE);
  Push(5, ts, ActionType::PLACE);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::MULTI_SUBMIT_RESPONSE;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::CancelSingleOrder(
    const RestParams& params) {
  auto ts = GetCurrentTimestamp();

  UrlParamsBuilder builder;
  builder.PutGet("symbol", params.symbol());
  builder.PutGet("orderId", params.order_id());
  builder.PutGet("origClientOrderId", params.orig_client_order_id());
  builder.PutGet("newClientOrderId", params.new_client_order_id());
  builder.PutGet("recvWindow", params.recv_window());
  builder.PutGet("timestamp", GetCurrMilliseconds());
  auto context = std::shared_ptr<AsioHttpContext>(
      GetFirstAuth()->CreateRequestByDeleteWithSignature(place_single_order_path_, &builder));
  SetBindIp(context->bind_ip);
  CHECK_API_LIMIT(ActionType::CANCEL);
  Push(1, ts, ActionType::CANCEL);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::CANCEL_RESPONSE;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::CancelBatchOrders(
    const RestParams& params) {
  CHECK_NE(params.order_ids().empty(), params.client_oids().empty())
      << "only one of order_ids and client_oids can be set!" << params.DebugString();

  auto ts = GetCurrentTimestamp();

  rapidjson::StringBuffer str_buf;
  rapidjson::Writer<rapidjson::StringBuffer> writer(str_buf);
  writer.StartArray();

  for (const auto& order_id : params.order_ids()) {
    writer.Int64(stoll(order_id));
  }

  for (const auto& client_oid : params.client_oids()) {
    writer.String(client_oid.c_str());
  }

  writer.EndArray();

  UrlParamsBuilder builder;
  builder.PutGet("symbol", params.symbol());
  const std::string encoded_ids = ApiSignatureUtil::EscapeURL(str_buf.GetString());
  if (!params.order_ids().empty()) {
    builder.PutGet("orderIdList", encoded_ids);
  }
  if (!params.client_oids().empty()) {
    builder.PutGet("origClientOrderIdList", encoded_ids);
  }
  builder.PutGet("recvWindow", params.recv_window());
  builder.PutGet("timestamp", GetCurrMilliseconds());

  auto context = std::shared_ptr<AsioHttpContext>(
      GetFirstAuth()->CreateRequestByDeleteWithSignature(place_batch_orders_path_, &builder));
  SetBindIp(context->bind_ip);
  CHECK_API_LIMIT(ActionType::CANCEL);
  Push(1, ts, ActionType::CANCEL);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::MULTI_CANCEL_RESPONSE;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::GenListenKey(bool is_init) {
  if (is_init && !listen_key_.empty()) {
    return nullptr;
  }
  auto ts = GetCurrentTimestamp();
  // CHECK_API_LIMIT(ActionType::QUERY);

  UrlParamsBuilder builder;
  auto context = std::shared_ptr<AsioHttpContext>(
      GetFirstAuth()->CreateRequestByPostWithSignature(listen_path_, &builder));
  SetBindIp(context->bind_ip);
  Push(1, ts, ActionType::QUERY);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::LISTEN_KEY;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::ExtendListenKey() {
  auto ts = GetCurrentTimestamp();
  // CHECK_API_LIMIT(ActionType::QUERY);

  UrlParamsBuilder builder;
  auto context = std::shared_ptr<AsioHttpContext>(
      GetFirstAuth()->CreateRequestByPutWithSignature(listen_path_, &builder));
  SetBindIp(context->bind_ip);
  Push(1, ts, ActionType::QUERY);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::LISTEN_KEY;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::DeleteListenKey() {
  auto ts = GetCurrentTimestamp();
  // CHECK_API_LIMIT(ActionType::QUERY);

  UrlParamsBuilder builder;
  auto context = std::shared_ptr<AsioHttpContext>(
      GetFirstAuth()->CreateRequestByDeleteWithSignature(listen_path_, &builder));
  SetBindIp(context->bind_ip);
  Push(1, ts, ActionType::QUERY);
  api_req_info_[__func__]++;

  context->query_type = OrderSubTopicId::LISTEN_KEY;
  context->extra_info = DebugString();
  return context;
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::PlaceOrderNorm(
    const OrderExecution& order_exe) {
  if (!order_exe.is_place_batch_orders) {
    const auto& order_spec = *order_exe.order_spec;
    const auto proc_order_id = order_exe.proc_order_id;
    auto params = GetRestOrderParams(order_spec, proc_order_id);
    return PlaceSingleOrder(params);
  }

  auto mutable_order_exe = const_cast<OrderExecution*>(&order_exe);

  if (mutable_order_exe->place_pending_order_deque.empty()) {
    return {};
  }

  std::vector<RestParams> params;
  for (int i = 0; i < 5 && !mutable_order_exe->place_pending_order_deque.empty(); ++i) {
    const auto* order_spec = mutable_order_exe->place_pending_order_deque.front();
    mutable_order_exe->place_pending_order_deque.pop_front();

    mutable_order_exe->place_proceed_order_deque.push_back(order_spec);

    params.emplace_back(GetRestOrderParams(*order_spec, order_spec->proc_order_id));
  }

  return PlaceBatchOrders(params);
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::CancelOrderNorm(
    const OrderExecution& order_exe) {
  if (!order_exe.is_cancel_batch_orders) {
    auto params = GetRestCancelParams(*order_exe.oc);
    return CancelSingleOrder(params);
  }

  auto mutable_order_exe = const_cast<OrderExecution*>(&order_exe);

  if (mutable_order_exe->cancel_pending_order_deque.empty()) {
    return {};
  }

  OrderExecution::OrderContextList temp_cancel_pending_order_deque;
  const auto& symbol = mutable_order_exe->cancel_pending_order_deque.front()->native_product();
  RestParams param;
  param.set_symbol(symbol);
  param.set_recv_window(5000);

  int cnt = 0;
  while (!mutable_order_exe->cancel_pending_order_deque.empty()) {
    const auto* order_context = mutable_order_exe->cancel_pending_order_deque.front();
    mutable_order_exe->cancel_pending_order_deque.pop_front();

    if (order_context->native_product() != symbol || cnt >= 10) {
      temp_cancel_pending_order_deque.emplace_back(order_context);
      continue;
    }

    mutable_order_exe->cancel_proceed_order_deque.emplace_back(order_context);
    ++cnt;

    if (order_context->has_exchange_order_id()) {
      param.add_order_ids(order_context->exchange_order_id().substr(symbol.length()));
    }

    param.add_client_oids(std::to_string(order_context->proc_order_id()));
  }

  mutable_order_exe->cancel_pending_order_deque = std::move(temp_cancel_pending_order_deque);
  if (param.order_ids_size() < param.client_oids_size()) {
    param.clear_order_ids();
  } else {
    param.clear_client_oids();
  }

  return CancelBatchOrders(param);
}

void BinanceFuturesNativePrivateClient::Prepare() {
  using coin2::exchange::binance::api_util::BinanceRateTracker;
  auto* tracker = dynamic_cast<BinanceRateTracker*>(this->rate_tracker_.get());
  mb_->Subscribe(
      "binance/req_rate_limit",
      std::bind(
          &BinanceRateTracker::UpdateReqRateInfo,
          tracker,
          std::placeholders::_1,
          std::placeholders::_2));
  mb_->Subscribe("binance/listen_key", [this](const std::string& channel, void* data) {
    listen_key_ = *static_cast<std::string*>(data);

    LOG(INFO) << "BinanceFuturesNativePrivateClient set listen key: " << channel << ", "
              << listen_key_;
  });
}

std::string BinanceFuturesNativePrivateClient::DebugString() const {
  double delta_t = (GetCurrentTimestamp() - start_time_) / 1.0e9;

  std::stringstream ss;
  ss << "BinanceFuturesNativePrivateClient::DebugString\n";
  ss << "api info\n";
  for (auto& [name, value] : api_req_info_) {
    int32_t blocked = 0;
    if (api_block_info_.count(name) > 0) {
      blocked = api_block_info_.at(name);
    }
    int32_t total_req = value + blocked;
    ss << name << " (total:" << total_req << ", succeeded: " << value << ", blocked: " << blocked
       << ", req_rate: " << value / delta_t << ", block_rate: " << blocked / delta_t << ")\n";
  }
  return ss.str();
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::SubmitOrderExecution(
    const OrderExecutionProto& order) {
  switch (order.type()) {
    case OrderExecutionProto::SUBMIT_ORDER: {
      auto order_spec = OrderSpec::FromProto(order.order_spec());
      auto params = GetRestOrderParams(*order_spec, order.proc_order_id());
      return PlaceSingleOrder(params);
    }
    case OrderExecutionProto::CANCEL_ORDER: {
      auto order_spec = OrderSpec::FromProto(order.order_spec());
      auto params = GetRestOrderParams(*order_spec, order.proc_order_id());
      return CancelSingleOrder(params);
    }
    default: {
      return nullptr;
    }
  }
}

std::shared_ptr<AsioHttpContext> BinanceFuturesNativePrivateClient::SubmitRestQuery(
    const RestQueryProto& query) {
  RestParams params;
  switch (query.type()) {
    case RestQueryProto::QUERY_ACCOUNT_BALANCE: {
      params.set_recv_window(5000);
      return QueryAccountBalanceAndAccountPosition(params);
    }
    case RestQueryProto::QUERY_ACCOUNT_POSITION: {
      params.set_recv_window(5000);
      return QueryAccountBalanceAndAccountPosition(params);
    }
    case RestQueryProto::QUERY_OPEN_ORDERS: {
      return QueryOpenOrders(params);
    }
    default: {
      return nullptr;
    }
  }
}

}  // namespace coin2::exchange::binance_futures::api_order
